/**
 * @author Tobias Boehm
 *
 * 09.04.2011
 */
package org.anddev.andengine.extension.tbsengine.menu.activity;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.anddev.andengine.extension.multiplayer.protocol.adt.message.IMessage;
import org.anddev.andengine.extension.multiplayer.protocol.adt.message.client.ClientMessage;
import org.anddev.andengine.extension.multiplayer.protocol.adt.message.server.IServerMessage;
import org.anddev.andengine.extension.multiplayer.protocol.adt.message.server.ServerMessage;
import org.anddev.andengine.extension.multiplayer.protocol.client.IServerMessageHandler;
import org.anddev.andengine.extension.multiplayer.protocol.client.connector.ServerConnector;
import org.anddev.andengine.extension.multiplayer.protocol.shared.Connection;
import org.anddev.andengine.extension.multiplayer.protocol.util.MessagePool;
import org.anddev.andengine.extension.tbsengine.client.AbsInGameActivity;
import org.anddev.andengine.extension.tbsengine.client.AbsServerMessageCopyHandler;
import org.anddev.andengine.extension.tbsengine.client.ClientConnectionGateway;
import org.anddev.andengine.extension.tbsengine.client.ClientConnectionGateway.IOnGameClientConnectionStatusListener;
import org.anddev.andengine.extension.tbsengine.faction.AbsBaseFaction;
import org.anddev.andengine.extension.tbsengine.map.AbsMapOverview;
import org.anddev.andengine.extension.tbsengine.match.playerslot.AbsClientPlayerSlot;
import org.anddev.andengine.extension.tbsengine.match.playerslot.AbsPlayerSlot.SlotState;
import org.anddev.andengine.extension.tbsengine.menu.view.IPrepareMatchView;
import org.anddev.andengine.extension.tbsengine.message.client.ClientChatmessageMessage;
import org.anddev.andengine.extension.tbsengine.message.client.ClientJoinOrLeaveServerRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.ClientMatchJoinLeaveRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.ClientMatchListRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.DefaultTBSclientMessageFlags;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientPrepareMatchRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientSlotChangeFactionRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientSlotChangePlayerRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientSlotChangeTeamReuqestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientSlotChangeToOpenOrClosedRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientStartMatchRequestMessage;
import org.anddev.andengine.extension.tbsengine.message.client.preparematch.ClientToggleReadyToPlayMessage;
import org.anddev.andengine.extension.tbsengine.message.server.DefaultTBSserverMessageFlags;
import org.anddev.andengine.extension.tbsengine.message.server.ServerChatMessageForwardMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ServerJoinOrLeaveServerReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.ServerMatchPlayerJoinedLeftAnnouncementMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.AbsServerMatchpreparationJoinRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.AbsServerMatchpreparationSlotChangedAnnouncementMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreparationPromotionMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreparationSlotChangeRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreprataionStartRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerMatchpreprataionStateChangedAnnouncement;
import org.anddev.andengine.extension.tbsengine.message.server.preparematch.ServerPrepareMatchRequestReplyMessage;
import org.anddev.andengine.extension.tbsengine.player.BaseHumanPlayer;
import org.anddev.andengine.extension.tbsengine.player.BasePlayer;
import org.anddev.andengine.extension.tbsengine.server.AbsGameServerService;
import org.anddev.andengine.extension.tbsengine.shared.MatchJoinOrPreparationType;
import org.anddev.andengine.extension.tbsengine.shared.NoMatchPreparationTypeProvidedException;
import org.anddev.andengine.extension.tbsengine.shared.TBSengineConstants;
import org.anddev.andengine.extension.tbsengine.team.Team;
import org.xml.sax.SAXException;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;

/**
 * This is an abstract representation of a match preparation activity. This class
 * should simplify your way to a fully featured match preparation lobby with 
 * player slots, match settings, chat and client <-> server communication.
 * <p>
 * Notice:<br>
 * At the moment this may be one of the most complex classes in the whole framework.
 * Please read all method comments carefully and look at the example implementation
 * at Google Code.
 * <p>
 * Do not forget to implement {@link AbsServerMatchpreparationJoinRequestReplyMessage},
 * and {@link AbsServerMatchpreparationSlotChangedAnnouncementMessage} and add
 * them by implementing {@link #registerAdditionalFlagsServerMessageClasses(Map)}. <br>
 * Also, you have to implement correlate {@link AbsServerMessageCopyHandler} for
 * them and add them by implementing {@link #registerAdditionalFlagsServerMessageHandlers(Map)}.
 * 
 * @author Tobias Boehm
 *
 * @param <MapOverviewClass> - Your implementation of {@link AbsMapOverview}.
 * @param <FactionBaseClass> - Your implementation of {@link AbsBaseFaction}.
 * @param <PlayerSlotClass> - Your implementation of {@link AbsClientPlayerSlot}
 * @param <PrepareMatchViewClass> - A view which implements {@link IPrepareMatchView}.
 */
public abstract class AbsPrepareMatchActivity<MapOverviewClass extends AbsMapOverview,
											  FactionBaseClass extends AbsBaseFaction,
											  PlayerSlotClass extends AbsClientPlayerSlot<FactionBaseClass, ?, ?>,
											  PrepareMatchViewClass extends View & IPrepareMatchView<?, MapOverviewClass>>

											  extends AbsFullscreenActivity implements DefaultTBSclientMessageFlags, DefaultTBSserverMessageFlags, IOnGameClientConnectionStatusListener, TBSengineConstants {
	
	/**
	 * This is a reference to the client connection gateway you should use to
	 * interact with the game server. 
	 */
	protected ClientConnectionGateway mClientConnectionGateway;
	
	/**
	 * This is a map of flags -> server message classes. You will need it to
	 * initialize the {@link #mClientConnectionGateway}.
	 */
	protected Map<Short,Class<? extends IServerMessage>> mFlagsServerMessageClassesMap;
	
	/**
	 * This is a map of flags -> server message handlers. You will need it to
	 * initialize the {@link #mClientConnectionGateway}.
	 */
	protected Map<Short,IServerMessageHandler> mFlagsServerMessageHandlersMap;
	
	/**
	 * This is the message pool for client messages. You should use this pool
	 * to obtain and recycle {@link ClientMessage}s.
	 */
	protected MessagePool<IMessage> mClientMessagePool;
	
	/**
	 * This is a flag which indicates if a server disconnection is expected and
	 * should not be handled as an error.
	 */
	protected boolean mServerDisconnectExcpected = false;
	
	/**
	 * This is the view which should represent the match preparation lobby and 
	 * all its components.
	 */
	protected PrepareMatchViewClass mPrepareMatchView;
	
	/**
	 * This is the join or preparation type this activity got started with.
	 */
	protected MatchJoinOrPreparationType mMatchPreparationType;
	
	/**
	 * A reference to the player who owns the device this client is running on.
	 */
	protected BaseHumanPlayer mMainLocalPlayer;
	
	/**
	 * The ID of the joined match.
	 */
	protected UUID mJoinedMatchID = null;
	
	
	/*
	 * The following member variables are equivalent to those inside AbsServerMatchPreparation.
	 * There will be an AbsClientMatchPreparation object in an later implementation.
	 */
	
	/**
	 * A map of playerIDs -> players who joined this match preparation.
	 */
	protected HashMap<UUID,BasePlayer> mPlayerIDsPlayers = new HashMap<UUID,BasePlayer>();
	
	/**
	 * A set of all factions which are available to the players.
	 */
	protected HashSet<FactionBaseClass> mFactions = new HashSet<FactionBaseClass>();
	
	/**
	 * A map of all available team IDs -> teams of this match preparation.
	 */
	protected HashMap<Integer,Team> mTeamIDsTeamsMap = new HashMap<Integer,Team>();
	
	/**
	 * A map of all existing playerSlot IDs -> player slot objects of this
	 * match preparation.
	 */
	protected Map<Long,PlayerSlotClass> mSlotIDsPlayerSlots = new HashMap<Long,PlayerSlotClass>();
	
	/**
	 * A reference to the map overview object of the map which is played.
	 */
	protected MapOverviewClass mMapOverview;
	
	
	/**
	 * This method you should load and return the map overview object of the
	 * chosen map.
	 * 
	 * @param pChosenMapID
	 * 
	 * @return the map overview object of the chosen map.
	 * 
	 * @throws IOException
	 */
	protected abstract MapOverviewClass loadChosenMapOverview(final String pChosenMapID) throws IOException, SAXException;
	
	/**
	 * This method should return an instance of a class which extends {@link View}
	 * and implements {@link IPrepareMatchView}.
	 * 
	 * @param pMatchPreparationType
	 * 
	 * @return an instance of a class which extends {@link View} and implements {@link IPrepareMatchView}.
	 */
	protected abstract PrepareMatchViewClass createPrepareMatchView(final MatchJoinOrPreparationType pMatchPreparationType);
	
	/**
	 * This method should return an intent which could be used to start an 
	 * implementation of {@link AbsInGameActivity}.
	 * 
	 * @return
	 */
	protected abstract Intent prepareInGameStartIntent();
	
	/**
	 * This method should return a class which extends {@link AbsGameServerService}.
	 * It will be used to start the corresponding service.
	 * <p>
	 * Notice:<br>
	 * Make sure your service class is registered inside the AndroidMainfest.xml
	 * 
	 * @return
	 */
	protected abstract Class<? extends AbsGameServerService> getGameServerServiceClass(); 
	
	/**
	 * This method can be used to register custom {@link ServerMessage} classes.
	 * <p>
	 * Notice:<br>
	 * You should implement {@link AbsServerMatchpreparationJoinRequestReplyMessage},
	 * and {@link AbsServerMatchpreparationSlotChangedAnnouncementMessage} and
	 * add them to the map.
	 * 
	 * 
	 * @see #registerAdditionalFlagsServerMessageHandlers(Map) 
	 *  
	 * @param pFlagsServerMessageClassesMap
	 */
	protected abstract void registerAdditionalFlagsServerMessageClasses(final Map<Short, Class<? extends IServerMessage>> pFlagsServerMessageClassesMap);

	/**
	 * This method can be used to register custom {@link AbsServerMessageCopyHandler}
	 * for custom {@link ServerMessage} classes.
	 * Notice:<br>
	 * You should implement custom {@link AbsServerMessageCopyHandler} for your
	 * implementation of {@link AbsServerMatchpreparationJoinRequestReplyMessage},
	 * and {@link AbsServerMatchpreparationSlotChangedAnnouncementMessage} and
	 * add them to the map.
	 * 
	 * 
	 * @see #registerAdditionalFlagsServerMessageClasses(Map)
	 * 
	 * @param pFlagsServerMessageHandlersMap
	 */
	protected abstract void registerAdditionalFlagsServerMessageHandlers(final Map<Short, IServerMessageHandler> pFlagsServerMessageHandlersMap);
	
	/**
	 * This method can be used to register custom {@link ClientMessage} classes.
	 * 
	 * @see #mClientMessagePool
	 * 
	 * @param pClientMessagePool
	 */
	protected abstract void registerAdditionalClientMessages(final MessagePool<IMessage> pClientMessagePool);
		
	/**
	 * This method should return an instance of {@link ClientConnectionGateway}.
	 * You may use the given parameters to decide what kind of gateway you
	 * want to create.<p>
	 * Notice:<br>
	 * "this" class is an {@link IOnGameClientConnectionStatusListener} and can be used
	 * for the {@link ClientConnectionGateway} constructor.
	 * 
	 * @param pMatchPreparationType
	 * @param pIntent
	 * @param pFlagsServerMessageClassesMap
	 * @param pFlagsServerMessageHandlersMap
	 * 
	 * @return an instance of {@link ClientConnectionGateway}.
	 * 
	 * @throws NumberFormatException
	 * @throws IOException
	 */
	protected abstract ClientConnectionGateway createClientConnectionGateway(final MatchJoinOrPreparationType pMatchPreparationType, final Intent pIntent,final Map<Short, Class<? extends IServerMessage>> pFlagsServerMessageClassesMap, final Map<Short, IServerMessageHandler> pFlagsServerMessageHandlersMap) throws NumberFormatException, IOException;
	
	/**
	 * This method should be used to load the player who owns the device this
	 * client is running on.
	 * 
	 * @return
	 */
	protected abstract BaseHumanPlayer loadMainLocalPlayer();
	
	/**
	 * This method should handle incoming {@link ServerJoinOrLeaveServerReplyMessage}s.
	 * <p>
	 * You may want to send a {@link ClientMatchListRequestMessage} or a
	 * {@link ClientPrepareMatchRequestMessage} to the server depending on the
	 * given {@link MatchJoinOrPreparationType}.
	 * 
	 * @param pMessage
	 * @param pPreparationType
	 */
	protected abstract void onJoinOrLeaveServerReply(final ServerJoinOrLeaveServerReplyMessage pMessage, final MatchJoinOrPreparationType pPreparationType);
	
	/**
	 * This method should handle incoming {@link ServerPrepareMatchRequestReplyMessage}s.
	 * <p>
	 * You may want to send a {@link ClientMatchJoinLeaveRequestMessage} to the
	 * server to join the match you created or to inform the user about an error.
	 * 
	 * @param pMessage
	 * @param pPreparationType
	 */
	protected abstract void onPrepareMatchServerReply(final ServerPrepareMatchRequestReplyMessage pMessage, final MatchJoinOrPreparationType pPreparationType);
	
	/**
	 * This method should handle incoming {@link ServerMatchpreprataionStateChangedAnnouncement}s.
	 * 
	 * @param pMessage
	 */
	protected abstract void onMatchStateChangedAnnouncement(final ServerMatchpreprataionStateChangedAnnouncement pMessage);
	
	/**
	 * This method should handle incoming {@link ServerMatchPlayerJoinedLeftAnnouncementMessage}s.
	 * <p>
	 * You may want to add/remove the player from the {@link #mPlayerIDsPlayers}
	 * map and to inform the {@link #mPrepareMatchView} about the change.
	 * 
	 * @param pMessage
	 */
	protected abstract void onPlayerJoinedLeftMatchAnnouncement(final ServerMatchPlayerJoinedLeftAnnouncementMessage pMessage);
	
	/**
	 * This method should handle incoming {@link ServerMatchpreparationPromotionMessage}s.
	 * <p>
	 * You may want to send a {@link ClientMatchJoinLeaveRequestMessage} to the
	 * server depending on the given {@link MatchJoinOrPreparationType} or to
	 * inform the user about the incoming match offer.
	 * 
	 * @param pMessage
	 * @param pPreparationType
	 */
	protected abstract void onServerMatchPromotion(final ServerMatchpreparationPromotionMessage pMessage, final MatchJoinOrPreparationType pPreparationType);
	
	/**
	 * This method should handle incoming {@link ServerChatMessageForwardMessage}s.
	 * <p>
	 * You may want to inform the {@link #mPrepareMatchView} about the incoming
	 * chat message.
	 * 
	 * @param pMessage
	 */
	protected abstract void onServerChatMessageForward(final ServerChatMessageForwardMessage pMessage);
		
	@Override
	protected void onDestroy() {
		
		if((this.mClientConnectionGateway != null) && this.mClientConnectionGateway.isConnected()){
			
			final ClientJoinOrLeaveServerRequestMessage message = (ClientJoinOrLeaveServerRequestMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_JOIN_OR_LEAVE_SERVER_REQUEST);
			
			message.setJoinServer(false);
			message.setPlayer(this.mMainLocalPlayer);
			
			try {
				this.mClientConnectionGateway.sendClientMessage(message);
			} catch (IOException e) {
				e.printStackTrace();
			}finally{
				
				this.mClientMessagePool.recycleMessage(message);
			}
		}
		
		super.onDestroy();		
	}
	

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		// Get the MatchPreparationType
		try {
						
			this.mMatchPreparationType = MatchJoinOrPreparationType.valueOf(getIntent().getStringExtra(TBSE_MATCH_JOIN_OR_PREPARATION_TYPE_INTENT_EXTRA));
			
		}catch(Exception e){
			
			throw new NoMatchPreparationTypeProvidedException();
		}		
				
		// Prepare GameClient and sometimes the Server 
		this.toggleGameServerService();
		
		// Load main local Player
		this.mMainLocalPlayer = this.loadMainLocalPlayer();
		
		// Init ClientMessagePool
		this.initClientMessagePool();
				
		// Init Handlers
		this.initServerMessagesAndHandlers();
		
		// Prepare View
		this.mPrepareMatchView = this.createPrepareMatchView(this.mMatchPreparationType);
		this.setContentView(this.mPrepareMatchView);
		
		// Create and connect Game client
		try {
			this.mClientConnectionGateway = this.createClientConnectionGateway(this.mMatchPreparationType, getIntent(), this.mFlagsServerMessageClassesMap, this.mFlagsServerMessageHandlersMap);
			this.mClientConnectionGateway.asyncConnect();
						
			// Inform View about connection start to server
			this.mPrepareMatchView.onStartConnectingToGameServer();
			
		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
		
	private void toggleGameServerService() {

		final Intent serviceIntent = new Intent(this, getGameServerServiceClass());
		
		switch(this.mMatchPreparationType){
		
			case TBSE_PREPARE_SKIRMISH_MATCH:
			case TBSE_PREPARE_HOTSEAT_MATCH:
			case TBSE_PREPARE_BLUETOOTH_MATCH:
			case TBSE_PREPARE_WLAN_MATCH:
				
				serviceIntent.putExtra(TBSE_MATCH_JOIN_OR_PREPARATION_TYPE_INTENT_EXTRA, this.mMatchPreparationType.name());
				this.startService(serviceIntent);
				break;		
				
			default:
				this.stopService(serviceIntent);
		}		
	}
	
	/**
	 * This method will start your implementation of {@link AbsInGameActivity}.
	 * You should call this class if you receive proper {@link ServerMatchpreprataionStateChangedAnnouncement}.
	 */
	protected void startInGameActivity(){
		
		this.mServerDisconnectExcpected = true;
		
		this.mClientConnectionGateway.disconnectFromServer();
		
		final Intent intent = this.prepareInGameStartIntent();
				
		this.startActivity(intent);
	}
	
	private void initClientMessagePool() {
		
		this.mClientMessagePool = new MessagePool<IMessage>();
		
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_CHATMESSAGE, ClientChatmessageMessage.class);
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_MATCH_JOIN_LEAVE_REQUST, ClientMatchJoinLeaveRequestMessage.class);
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_PREPARE_MATCH_REQUEST, ClientPrepareMatchRequestMessage.class);
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_TOGGLE_READY_TO_PLAY_ANNOUNCEMENT, ClientToggleReadyToPlayMessage.class);
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_SLOT_CHANGE_FACTION_REQUEST, ClientSlotChangeFactionRequestMessage.class);
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_SLOT_CHANGE_PLAYER_REQUEST, ClientSlotChangePlayerRequestMessage.class);
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_SLOT_CHANGE_TEAM_REQUEST, ClientSlotChangeTeamReuqestMessage.class);
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_SLOT_CHANGE_TO_OPEN_OR_CLOSED_REQUEST, ClientSlotChangeToOpenOrClosedRequestMessage.class);
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_JOIN_OR_LEAVE_SERVER_REQUEST, ClientJoinOrLeaveServerRequestMessage.class);
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_MATCH_LIST_REQUEST, ClientMatchListRequestMessage.class);
		this.mClientMessagePool.registerMessage(FLAG_CLIENT_START_MATCH_REQUEST, ClientStartMatchRequestMessage.class);		
		
		this.registerAdditionalClientMessages(this.mClientMessagePool);
	}
	
	private void initServerMessagesAndHandlers() {

		this.mFlagsServerMessageClassesMap = this.createFlagsServerMessageClassesMap();
		
		this.registerAdditionalFlagsServerMessageClasses(this.mFlagsServerMessageClassesMap);
		
		this.mFlagsServerMessageHandlersMap = this.createFlagsServerMessageHandlersMap();
		
		this.registerAdditionalFlagsServerMessageHandlers(this.mFlagsServerMessageHandlersMap);
	}
	
	private Map<Short, Class<? extends IServerMessage>> createFlagsServerMessageClassesMap() {
		
		final HashMap<Short, Class<? extends IServerMessage>> map = new HashMap<Short, Class<? extends IServerMessage>>();
		
		map.put(FLAG_SERVER_CHAT_MESSAGE_FORWARD, ServerChatMessageForwardMessage.class);
		map.put(FLAG_SERVER_MATCHPREPARATION_PROMOTION, ServerMatchpreparationPromotionMessage.class);
		map.put(FLAG_SERVER_MATCH_PLAYER_JOINED_LEFT_ANNOUNCEMENT, ServerMatchPlayerJoinedLeftAnnouncementMessage.class);
		map.put(FLAG_SERVER_MATCHPREPARATION_REQUEST_REPLY, ServerPrepareMatchRequestReplyMessage.class);
		map.put(FLAG_SERVER_JOIN_OR_LEAVE_SERVER_REPLY, ServerJoinOrLeaveServerReplyMessage.class);
		map.put(FLAG_SERVER_MATCHPREPARATION_SLOT_CHANGE_REQUEST_REPLY, ServerMatchpreparationSlotChangeRequestReplyMessage.class);
		map.put(FLAG_SERVER_MATCHPREPARATION_STATE_CHANGED_ANNOUNCEMENT, ServerMatchpreprataionStateChangedAnnouncement.class);
		map.put(FLAG_SERVER_MATCHPREPARATION_START_REQUEST_REPLY, ServerMatchpreprataionStartRequestReplyMessage.class);		
		
		return map;
	}
	
	private Map<Short, IServerMessageHandler> createFlagsServerMessageHandlersMap() {
		
		final HashMap<Short, IServerMessageHandler> map = new HashMap<Short, IServerMessageHandler>();
		
		map.put(FLAG_SERVER_MATCHPREPARATION_START_REQUEST_REPLY, new AbsServerMessageCopyHandler<ServerMatchpreprataionStartRequestReplyMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerMatchpreprataionStartRequestReplyMessage pMessage) {
				
				onServerMatchStartRequestReply(pMessage);
			}
		});
		
		map.put(FLAG_SERVER_MATCHPREPARATION_STATE_CHANGED_ANNOUNCEMENT, new AbsServerMessageCopyHandler<ServerMatchpreprataionStateChangedAnnouncement, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerMatchpreprataionStateChangedAnnouncement pMessage) {
				
				onMatchStateChangedAnnouncement(pMessage);
			}
		});
		
		map.put(FLAG_SERVER_CHAT_MESSAGE_FORWARD, new AbsServerMessageCopyHandler<ServerChatMessageForwardMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerChatMessageForwardMessage pMessage) {
				
				onServerChatMessageForward(pMessage);
			}
		});
				
		map.put(FLAG_SERVER_MATCHPREPARATION_PROMOTION, new AbsServerMessageCopyHandler<ServerMatchpreparationPromotionMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerMatchpreparationPromotionMessage pMessage) {
				
				onServerMatchPromotion(pMessage, mMatchPreparationType);
			}
		});
				
		map.put(FLAG_SERVER_MATCH_PLAYER_JOINED_LEFT_ANNOUNCEMENT, new AbsServerMessageCopyHandler<ServerMatchPlayerJoinedLeftAnnouncementMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerMatchPlayerJoinedLeftAnnouncementMessage pMessage) {
				
				onPlayerJoinedLeftMatchAnnouncement(pMessage);
			}
		});
				
		map.put(FLAG_SERVER_MATCHPREPARATION_REQUEST_REPLY, new AbsServerMessageCopyHandler<ServerPrepareMatchRequestReplyMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerPrepareMatchRequestReplyMessage pMessage) {
				
				onPrepareMatchServerReply(pMessage, mMatchPreparationType);
			}
		});
				
		map.put(FLAG_SERVER_JOIN_OR_LEAVE_SERVER_REPLY, new AbsServerMessageCopyHandler<ServerJoinOrLeaveServerReplyMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerJoinOrLeaveServerReplyMessage pMessage) {
				
				onJoinOrLeaveServerReply(pMessage, mMatchPreparationType);
			}
		});
			
		map.put(FLAG_SERVER_MATCHPREPARATION_SLOT_CHANGE_REQUEST_REPLY, new AbsServerMessageCopyHandler<ServerMatchpreparationSlotChangeRequestReplyMessage, Connection>() {

			@Override
			public void onHandleMessageCopy(final ServerConnector<Connection> pServerConnector, final ServerMatchpreparationSlotChangeRequestReplyMessage pMessage) {
				
				onSlotChangeRequestReply(pMessage);
			}
		});
				
		return map;
	}

	private void onSlotChangeRequestReply(final ServerMatchpreparationSlotChangeRequestReplyMessage pMessage) {
			
		this.runOnUiThread(new Runnable() {
			
			@Override
			public void run() {
				
				mPrepareMatchView.onSlotChangeRequestReply(pMessage.isAccepted(), pMessage.getReason());
			}
		});
	}
	
	private void onServerMatchStartRequestReply(final ServerMatchpreprataionStartRequestReplyMessage pMessage) {
		
		this.runOnUiThread(new Runnable() {
			
			@Override
			public void run() {

				mPrepareMatchView.onServerMatchStartRequestReply(pMessage.isAccepted(), pMessage.getReason());				
			}
		});
				
		if(pMessage.isAccepted()){
		
			this.startInGameActivity();
		}
	}
	
	@Override
	public void onGameServerConnected() {
		
		this.runOnUiThread(new Runnable() {
			
			@Override
			public void run() {

				mPrepareMatchView.onGameServerConnected();
			}
		});
				
		final ClientJoinOrLeaveServerRequestMessage message = (ClientJoinOrLeaveServerRequestMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_JOIN_OR_LEAVE_SERVER_REQUEST);
		
		message.setPlayer(this.mMainLocalPlayer);
		message.setJoinServer(true);
		
		try {
			this.mClientConnectionGateway.sendClientMessage(message);
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			this.mClientMessagePool.recycleMessage(message);
		}
	}
	
	@Override
	public void onGameServerDisconnected() {
		
		if(!this.mServerDisconnectExcpected){
			
			this.runOnUiThread(new Runnable() {
				
				@Override
				public void run() {
					
					mPrepareMatchView.onGameServerDisconnected();
				}
			});
		}	
	}
	

	@Override
	public void onSocketGameServerNotReachable(final String pDestinationIP,
			final int pDestinationPort) {
		
		this.runOnUiThread(new Runnable() {
			
			@Override
			public void run() {
				mPrepareMatchView.onGameServerNotReachable(pDestinationIP,pDestinationPort);	
			}
		});
	}
	
	/**
	 * Call this method if you want to send a {@link ClientSlotChangeToOpenOrClosedRequestMessage}
	 * to the game server.
	 * 
	 * @param pSlotID
	 * @param pNewSlotState
	 */
	public void sendRequestSlotToOpenOrCloseMessage(final long pSlotID, final SlotState pNewSlotState){
	
		final ClientSlotChangeToOpenOrClosedRequestMessage message = (ClientSlotChangeToOpenOrClosedRequestMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_SLOT_CHANGE_TO_OPEN_OR_CLOSED_REQUEST);
		
		message.setMatchID(this.mJoinedMatchID);
		message.setPlayer(this.mMainLocalPlayer);
		message.setSlotID(pSlotID);
		message.setNewSlotState(pNewSlotState);
		
		try {
			this.mClientConnectionGateway.sendClientMessage(message);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			this.mClientMessagePool.recycleMessage(message);
		}
	}
	
	/**
	 * Call this method if you want to send a {@link ClientSlotChangePlayerRequestMessage}
	 * to the server.
	 * 
	 * @param pSlotID
	 * @param pNewPlayer
	 */
	public void sendRequestSlotPlayerChangeMessage(final long pSlotID, final BasePlayer pNewPlayer){
		
		final ClientSlotChangePlayerRequestMessage message = (ClientSlotChangePlayerRequestMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_SLOT_CHANGE_PLAYER_REQUEST);
		
		message.setMatchID(this.mJoinedMatchID);
		message.setPlayer(this.mMainLocalPlayer);
		message.setSlotID(pSlotID);
		message.setNewPlayer(pNewPlayer);
				
		try {
			this.mClientConnectionGateway.sendClientMessage(message);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			this.mClientMessagePool.recycleMessage(message);
		}
	}
	
	/**
	 * Call this method if you want to send a {@link ClientSlotChangeTeamReuqestMessage}
	 * to the server.
	 * 
	 * @param pSlotID
	 * @param pTeamID
	 */
	public void sendRequestSlotTeamChangeMessage(final long pSlotID, final Team pTeamID){
		
		final ClientSlotChangeTeamReuqestMessage message = (ClientSlotChangeTeamReuqestMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_SLOT_CHANGE_TEAM_REQUEST);
		
		message.setMatchID(this.mJoinedMatchID);
		message.setPlayer(this.mMainLocalPlayer);
		message.setSlotID(pSlotID);
		message.setNewTeam(pTeamID);
		
		try {
			this.mClientConnectionGateway.sendClientMessage(message);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			this.mClientMessagePool.recycleMessage(message);
		}
	}
	
	/**
	 * Call this method if you want to send a {@link ClientSlotChangeFactionRequestMessage} 
	 * to the server
	 * 
	 * @param pSlotID
	 * @param pNewFaction
	 */
	public void sendRequestSlotFactionChangeMessage(final long pSlotID, final AbsBaseFaction pNewFaction){
		
		final ClientSlotChangeFactionRequestMessage message = (ClientSlotChangeFactionRequestMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_SLOT_CHANGE_FACTION_REQUEST);
		
		message.setMatchID(this.mJoinedMatchID);
		message.setPlayer(this.mMainLocalPlayer);
		message.setSlotID(pSlotID);
		message.setNewFaction(pNewFaction);
		
		try {
			this.mClientConnectionGateway.sendClientMessage(message);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			this.mClientMessagePool.recycleMessage(message);
		}
	}
	
	/**
	 * Call this method if you want to toggle the status of the {@link #mMainLocalPlayer}.
	 * 
	 * @param pIsReady
	 */
	public void sendPlayerToggledReadyMessage(final boolean pIsReady){
		
		final ClientToggleReadyToPlayMessage message = (ClientToggleReadyToPlayMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_TOGGLE_READY_TO_PLAY_ANNOUNCEMENT);
		
		message.setPlayer(this.mMainLocalPlayer);
		message.setReady(pIsReady);
		message.setMatchID(this.mJoinedMatchID);
		
		try {
			this.mClientConnectionGateway.sendClientMessage(message);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			this.mClientMessagePool.recycleMessage(message);
		}
	}
	
	/**
	 * Call this method if your {@link #mMainLocalPlayer} is the owner of the
	 * match you are connected to and you want to start the match.
	 */
	public void sendStartMatchRequest(){
		
		final ClientStartMatchRequestMessage message = (ClientStartMatchRequestMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_START_MATCH_REQUEST);
		
		message.setPlayer(this.mMainLocalPlayer);
		message.setMatchID(this.mJoinedMatchID);
		
		try {
			this.mClientConnectionGateway.sendClientMessage(message);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			this.mClientMessagePool.recycleMessage(message);
		}
	}
	
	/**
	 * Call this method if you want to send a {@link ClientChatmessageMessage}
	 * to the server.
	 * 
	 * @param pMessage
	 * @param pRecipients
	 */
	public void sendChatMessage(final String pMessage, final List<BasePlayer> pRecipients){
		
		final ClientChatmessageMessage message = (ClientChatmessageMessage) this.mClientMessagePool.obtainMessage(FLAG_CLIENT_CHATMESSAGE);
		
		message.setPlayer(this.mMainLocalPlayer);
		message.setMessage(pMessage);
		message.setRecipients(pRecipients);
		message.setMatchID(this.mJoinedMatchID);
		
		try {
			this.mClientConnectionGateway.sendClientMessage(message);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			this.mClientMessagePool.recycleMessage(message);
		}
	}
	
	/**
	 * @return the mFactions
	 */
	public HashSet<FactionBaseClass> getFactions() {
		return mFactions;
	}

	/**
	 * @return the mTeams
	 */
	public Collection<Team> getTeams() {
		return mTeamIDsTeamsMap.values();
	}

	/**
	 * @return the mPlayers
	 */
	public HashMap<UUID, BasePlayer> getPlayerIDsPlayersMap() {
		return mPlayerIDsPlayers;
	}

	/**
	 * @return the mPlayerSlots
	 */
	public Collection<PlayerSlotClass> getPlayerSlots() {
		return mSlotIDsPlayerSlots.values();
	}

	/**
	 * @return the mMainLocalPlayer
	 */
	public BaseHumanPlayer getMainLocalPlayer() {
		return mMainLocalPlayer;
	}
}
